﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://wpfimageviewer.codeplex.com
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using BitmapTagger;
using BrainTechLLC;
using BrainTechLLC.FilterExtensions;
using BrainTechLLC.ThreadSafeObjects;
using BrainTechLLC.WPF;
using ImageAppDataModels;
using System.Collections.Specialized;
using BrainTechLLC.ImageOperatorsNonAForgeDependent;
using System.Reflection;

namespace ImageAppViewModels
{
    public class FileImageViewModel : GenericViewModelBase<FileImageViewModel, FileImage>, ICanBePreloaded<Bitmap>, INotifyPropertyChanged, IHasFileBackingStore, ICanBeSelected
    {
        public static LazyLoader<FileImageViewModel> LazySimilarity = new LazyLoader<FileImageViewModel>((fiAction, t) => DoSimilarityCheck(t, true), false, ThreadPriority.Normal); //ThreadPriority.BelowNormal);
        public static LazyLoader<FileImageViewModel> LazyBlur = new LazyLoader<FileImageViewModel>((fiAction, t) => DoBlurCheck(t), false, ThreadPriority.Normal); //ThreadPriority.BelowNormal);
        public FileImageCollectionViewModel OwningCollection;
        public string _NewFileName;
        public bool _EnteredFileNameEditMode;
        public bool _DeselectedByUser;
        public int _DesiredOrdering;
        public string _DisplayingGroups;
        public int _AddedIndexAndOrdering;
        
        public string DisplayingGroups
        {
            get { return _DisplayingGroups; }
            set
            {
                if (_DisplayingGroups != value)
                {
                    _DisplayingGroups = value;
                    OnPropertyChanged("DisplayingGroups");
                }
            }
        }

        public string SortOrder1
        {
            get
            {
                if (_DesiredOrdering != 0)
                {
                    return _DesiredOrdering.ToString().PadLeft(20, '0');
                }
                var sortBy = Settings.Values.SortBy;
                if (!string.IsNullOrEmpty(sortBy))
                {
                    PropertyInfo pi = typeof(FileImageViewModel).GetProperty(sortBy);
                    if (pi == null)
                    {
                        return FileName;
                    }
                    var obj = pi.GetValue(this, null);
                    return obj.ToString();
                }
                return FileName;
            }
        }

        public string SortOrder2
        {
            get
            {
                if (_DesiredOrdering != 0)
                {
                    return _DesiredOrdering.ToString().PadLeft(20, '0');
                }
                var sortBy2 = Settings.Values.SortBy2;
                if (!string.IsNullOrEmpty(sortBy2))
                {
                    PropertyInfo pi = typeof(FileImageViewModel).GetProperty(sortBy2);
                    if (pi == null)
                    {
                        return FileName;
                    }
                    var obj = pi.GetValue(this, null);
                    return obj.ToString();
                }
                return FileName;
            }
        }

        public int DesiredOrdering
        {
            get { return _DesiredOrdering; }
            set
            {
                if (_DesiredOrdering != value)
                {
                    _DesiredOrdering = value;
                    OnPropertyChanged("DesiredOrdering");
                }
            }
        }

        public void SetDesiredOrderingSilent(int desired)
        {
            _DesiredOrdering = desired;
        }

        public string NewFileName
        {
            get { return _NewFileName; }
            set
            {
                if (_NewFileName != value)
                {
                    _NewFileName = value;
                    OnPropertyChanged("NewFileName");
                    EnteredFileNameEditMode = false;
                }
            }
        }

        public Visibility EnteredFileNameEditModeVis
        {
            get { return EnteredFileNameEditMode ? Visibility.Visible : Visibility.Collapsed; ;}
        }

        public bool EnteredFileNameEditMode
        {
            get { return _EnteredFileNameEditMode; }
            set
            {
                if (_EnteredFileNameEditMode != value)
                {
                    _EnteredFileNameEditMode = value;
                    OnPropertyChanged("EnteredFileNameEditMode");
                    OnPropertyChanged("EnteredFileNameEditModeVis");
                }
            }
        }

        /// <summary>
        /// Raised when a thumbnail is being loaded ??? confirm
        /// </summary>
        public event EventHandler<ObjectEventArgs<FileImage>> SetThumbEvent;

        /// <summary>
        /// Raised when all rotation and resizing has completed
        /// </summary>
        public event EventHandler<ObjectEventArgs<FileImage>> RotateOrResizeDone;

        /// <summary>
        /// Has the file image view model been visible since last clenaup pass?
        /// </summary>
        public bool _hasBeenVisible;

        /// <summary>
        /// Cached similarity to other image (by file name)
        /// </summary>
        protected ThreadSafeLookupOnDemand<string, SimilarityMeasure> _similarity = new ThreadSafeLookupOnDemand<string, SimilarityMeasure>();

        /// <summary>
        /// File name without the path
        /// </summary>
        protected string _fileNameOnly;

        /// <summary>
        /// Temporary variable that stores the other file image view model for similarity measurement
        /// </summary>
        protected FileImageViewModel _other;

        /// <summary>
        /// Temporary variable that stores the other file image file name for similarity measurement
        /// </summary>
        protected string _currentOther;

        /// <summary>
        /// True if the thumbnail is visible on the screen, otherwise false
        /// </summary>
        protected bool _thumbVisible;

        /// <summary>
        /// Cached thumbnail image source
        /// </summary>
        protected ImageSource _thumbImageSourceCached;

        /// <summary>
        /// True if the view needs to be refreshed, otherwise false
        /// </summary>
        protected bool _dirty;

        /// <summary>
        /// Full path and filename
        /// </summary>
        public string _fileName;

        /// <summary>
        /// True if the viewmodel's thumbnail is currently visible
        /// </summary>
        protected bool _visible;

        /// <summary>
        /// Blur measure
        /// </summary>
        protected double _blur = double.NaN;

        /// <summary>
        /// Flag indicating this image should be included in comparison ops
        /// </summary>
        protected bool _wantCompare;

        /// <summary>
        /// Current image height
        /// </summary>
        protected double _currentHeight;

        /// <summary>
        /// Current image width
        /// </summary>
        protected double _currentWidth;

        /// <summary>
        /// Temporary variable that holds the other similarity groups to which this image belongs
        /// </summary>
        protected List<string> _similarityGroups;

        /// <summary>
        /// Show the similarity bar or not?
        /// </summary>
        protected bool _showSimilarityBar;

        /// <summary>
        /// Thumbnail image source cache dirty?
        /// </summary>
        protected bool _thumbImageSourceCachedDirty;

        protected bool _SelectedByUser;
        protected bool _IsSelected;

        public string CurrentWidthHeight
        {
            get { return string.Format("{0}x{1}", _currentWidth, _currentHeight); }
        }

        /// <summary>
        /// Has the file been deleted?
        /// </summary>
        public bool Deleted
        {
            get { return Item.Deleted; }
        }

        public bool ShowSimilarityBar
        {
            get { return _showSimilarityBar; }
            set
            {
                if (_showSimilarityBar != value)
                {
                    _showSimilarityBar = value;
                    OnPropertyChanged("ShowSimilarityBar");
                }
            }
        }

        /// <summary>
        /// True if the view needs to be refreshed, otherwise false
        /// </summary>
        public bool Dirty
        {
            get { return _dirty; }
            set { _dirty = value; }
        }

        public SingleThumbnail ThumbnailObject
        {
            get { return FileImage.ThumbnailObject; }
            set { FileImage.ThumbnailObject = value; }
        }

        public bool SelectedByUser
        {
            get { return _SelectedByUser; }
            set
            {
                if (_SelectedByUser != value)
                {
                    _SelectedByUser = value;
                    OnPropertyChanged("SelectedByUser");
                }
            }
        }

        public bool DeselectedByUser
        {
            get { return _DeselectedByUser; }
            set
            {
                if (_DeselectedByUser != value)
                {
                    _DeselectedByUser = value;
                    OnPropertyChanged("DeselectedByUser");
                }
            }
        }

        public bool IsSelected
        {
            get { return _IsSelected; }
            set
            {
                if (_IsSelected != value)
                {
                    _IsSelected = value;
                    OnPropertyChanged("IsSelected");
                }
            }
        }

        public void SilentSetIsSelected(bool value)
        {
            _IsSelected = value;
        }

        /// <summary>
        /// Gets whether the thumbnail is visible
        /// </summary>
        public bool ThumbVisible
        {
            get { return _thumbVisible; }
            set
            {
                if (_thumbVisible != value)
                {
                    // Console.WriteLine(string.Format("{0} became ThumbVisible = {1}", FileName, value));
                    _thumbVisible = value;

                    if (value)
                    {
                        _hasBeenVisible = true;
                    }

                    OnPropertyChanged("ThumbVisible");
                }
            }
        }

        /// <summary>
        /// Returns the Similarity rating with the current 'other' file image view model
        /// </summary>
        public double Similarity
        {
            get
            {
                if (string.IsNullOrEmpty(CurrentOther))
                    return 0D;

                var sim = _similarity.Get(CurrentOther, (s) => { return new SimilarityMeasure() { Similarity = 0D }; });

                return sim.Similarity;
            }
            set
            {
                if (string.IsNullOrEmpty(CurrentOther))
                    return;

                var sim = _similarity.Get(_currentOther, (s) => { return new SimilarityMeasure() { Similarity = 0D }; });

                if (sim.Similarity != value)
                {
                    sim.Similarity = value;
                    OnPropertyChanged("Similarity");
                }
            }
        }

        public double Blur
        {
            get { return _blur; }
            set
            {
                if (_blur != value)
                {
                    _blur = value;
                    OnPropertyChanged("Blur");
                }
            }
        }

        public string CurrentOther
        {
            get { return _currentOther; }
            set
            {
                if (_currentOther != value)
                {
                    _currentOther = value;
                    OnPropertyChanged("CurrentOther");
                    OnPropertyChanged("Similarity");
                }
            }
        }

        public Visibility ThumbShowDimensionsVisible
        {
            get { return GetVisibility(Settings.Values.ThumbShowDimensions); }
        }

        public Visibility ThumbShowFullPathVisible
        {
            get { return GetVisibility(Settings.Values.ThumbShowFullPath); }
        }

        public Visibility ThumbShowFileDatesVisible
        {
            get { return GetVisibility(Settings.Values.ThumbShowFileDate); }
        }

        public Visibility ThumbShowFileDescriptions
        {
            get { return GetVisibility(Settings.Values.ThumbShowFileDescriptions); }
        }

        public Visibility ThumbShowFileSizeVisible
        {
            get { return GetVisibility(Settings.Values.ThumbShowFileSize); }
        }

        public Visibility ThumbShowFileNameVisible
        {
            get { return GetVisibility(Settings.Values.ThumbShowFileName); }
        }

        public bool NoInfoDisplayedBelowThumbs
        {
            get { return Settings.Values.NoInfoDisplayedBelowThumbs; }
        }

        public double ThumbRowHeight
        {
            get { return Settings.Values.ThumbHeight - 56; }
        }

        public double ThumbColumnWidth
        {
            get { return Settings.Values.ThumbWidth - 18; }
        }

        public Settings CurrentSettings
        {
            get { return Settings.Values; }
        }

        /// <summary>
        /// True if the viewmodel's thumbnail is currently visible
        /// </summary>
        public bool Visible
        {
            get { return _visible; }
            set
            {
                if (_visible != value)
                {
                    _visible = value;
                    OnPropertyChanged("Visible");
                }
            }
        }

        public bool BitmapLoaded
        {
            get { return (Item.BitmapCached != null); }
        }

        public Bitmap Thumbnail
        {
            get { return Item.Thumbnail; }
            set
            {
                if (Item.ThumbnailCached != value)
                {
                    Item.Thumbnail = value;
                    OnPropertyChanged("ThumbLoaded");
                }
            }
        }

        public FileImageViewModel Other
        {
            get { return _other; }
            set { _other = value; }
        }

        public List<string> SimilarityGroups
        {
            get
            {
                if (_similarityGroups == null)
                    Interlocked.CompareExchange<List<string>>(ref _similarityGroups, new List<string>(), null);

                return _similarityGroups;
            }
            set
            {
                _similarityGroups = value;
            }
        }

        public List<SimilarityComparisonFiles> SimilarityComparisonFiles
        {
            get
            {
                List<SimilarityComparisonFiles> result = new List<SimilarityComparisonFiles>();
                result.Add(new SimilarityComparisonFiles() { FullFileName = string.Empty });
                var all = _similarity.AllKeyValuePairs;
                for (int n = 0; n < all.Count; n++)
                {
                    if (all[n].Value.Similarity.IsValidSimilarityMeasure())
                    {
                        result.Add(new SimilarityComparisonFiles() { FullFileName = all[n].Key });
                    }
                }
                result.Sort(new Comparison<SimilarityComparisonFiles>((s1, s2) => s1.FileNameOnly.CompareTo(s2.FileNameOnly)));
                return result;
            }
        }

        public string SimilarityGroupStrings
        {
            get
            {
                if (_similarityGroups == null)
                    return string.Empty;

                return _similarityGroups.BuildList((s) => s);
            }
        }

        public bool ThumbLoaded
        {
            get { return Item.ThumbnailCached != null; }
        }

        public double ThumbWidthPlus
        {
            get { return Settings.Values.ThumbWidth + 16; }
        }

        public double ThumbWidth
        {
            get { return Settings.Values.ThumbWidth - 8; }
        }

        public double ThumbHeight
        {
            get { return Settings.Values.ThumbHeight - 6; }
        }

        public double ThumbHeightPlus
        {
            get { return Settings.Values.ThumbHeight + 12; }
        }

        public bool WantCompare
        {
            get { return _wantCompare; }
            set
            {
                if (_wantCompare != value)
                {
                    _wantCompare = value;
                    OnPropertyChanged("WantCompare");
                }
            }
        }

        [RaisePropertyChangedForThisPropertyWhenOtherPropertyChanges("OriginalBitmapWidth")]
        public int LastOriginalBitmapWidth
        {
            get { return (Item as FileImage).OriginalBitmapWidth; }
        }

        [RaisePropertyChangedForThisPropertyWhenOtherPropertyChanges("OriginalBitmapHeight")]
        public int LastOriginalBitmapHeight
        {
            get { return (Item as FileImage).OriginalBitmapHeight; }
        }

        public ImageSource ImageSourceCached
        {
            get { return Item.ImageSourceCached; }
        }

        public bool ThumbImageSourceCachedDirty
        {
            get
            {
                return _thumbImageSourceCachedDirty;
            }
            set
            {
                if (_thumbImageSourceCachedDirty != value)
                {
                    _thumbImageSourceCachedDirty = value;
                    Dirty = true;
                }
            }
        }

        public ImageSource ThumbImageSourceCached
        {
            get { return _thumbImageSourceCached; }
            set { _thumbImageSourceCachedDirty = false; _thumbImageSourceCached = value; }
        }

        public ImageSource GetImageSource()
        {
            int width = (int)Math.Floor(_currentWidth);
            int height = (int)Math.Floor(_currentHeight);

            if (width <= 0 || height <= 0)
            {
                return Item.ImageSource;
            }
            else
            {
                return Item.GetImageSource(width, height, (fi) =>
                {
                    ImageSourceLoaded(fi as FileImage);
                });
            }
        }

        public ImageSource ImageSource
        {
            get
            {
                return GetImageSource();
            }
            set
            {
                Item.ImageSource = value;
            }
        }

        public bool ViewerIsTopmost
        {
            get { return Settings.Values.ViewerIsTopmost; }
            set
            {
                if (Settings.Values.ViewerIsTopmost != value)
                {
                    Settings.Values.ViewerIsTopmost = value;
                    OnPropertyChanged("ViewerIsTopmost");
                }
            }
        }

        public Visibility GetVisibility(bool b) { return b.GetVisibility(); }
        public FileImage FileImage { get { return Item; } }

        public string FileNameOnly
        {
            get
            {
                if (_fileNameOnly == null || _fileName != Item.FileName)
                {
                    FileNameOnly = System.IO.Path.GetFileName(Item.FileName);
                }

                return _fileNameOnly;
            }
            set
            {
                if (_fileNameOnly != value)
                {
                    _fileNameOnly = value;
                    OnPropertyChanged("FileNameOnly");
                }
            }
        }

        public string ImageDescription { get { return Item.ImageInfo.ImageDescription; } }
        public URational Aperture { get { return Item.ImageInfo.Aperture; } }
        public string Artist { get { return Item.ImageInfo.Artist; } }
        public Rational Brightness { get { return Item.ImageInfo.Brightness; } }
        public string Model { get { return Item.ImageInfo.Model; } }
        public ushort? MeteringMode { get { return Item.ImageInfo.MeteringMode; } }
        public string Make { get { return Item.ImageInfo.Make; } }
        public ushort? SensingMethod { get { return Item.ImageInfo.SensingMethod; } }
        public Rational ShutterSpeed { get { return Item.ImageInfo.ShutterSpeed; } }
        public string Software { get { return Item.ImageInfo.Software; } }
        public string UserComment { get { return Item.ImageInfo.UserComment; } }
        public ushort? LightSource { get { return Item.ImageInfo.LightSource; } }
        public ushort? ISOSpeedRatings { get { return Item.ImageInfo.ISOSpeedRatings; } }
        public URational ExposureTime { get { return Item.ImageInfo.ExposureTime; } }
        public URational FNumber { get { return Item.ImageInfo.FNumber; } }
        public ushort? ExposureProgram { get { return Item.ImageInfo.ExposureProgram; } }
        public DateTime? DateTimeOriginal { get { return Item.ImageInfo.DateTimeOriginal; } }

        public DateTime FileDateTime
        {
            get
            {
                FileImage fi = Item as FileImage;
                //if (DateTime == null)
                //{
                return fi.CreationTime;
                //}
                //return DateTime.Value;
            }
        }

        public DateTime? DateTimeDigitized { get { return Item.ImageInfo.DateTimeDigitized; } }
        public string Copyright { get { return Item.ImageInfo.Copyright; } }
        public DateTime? DateTime { get { return Item.ImageInfo.PhotoDateTime; } }

        public bool MeetsFilterCriteria(string filter, int minSize, int maxSize, List<GroupViewModel> isInGroupViewModel)
        {
            bool meetsGroup = true;
            GroupViewModel found = null;

            if (isInGroupViewModel != null)
            {
                for (int n = 0; n < isInGroupViewModel.Count; n++)
                {
                    var group = isInGroupViewModel[n];
                    if (group.FindFindImage(FileName) != null)
                    {
                        found = group;
                        break;
                    }
                }

                if (found == null)
                {
                    meetsGroup = false;
                }
            }

            if (!meetsGroup)
            {
                return false;
            }

            if (string.IsNullOrEmpty(filter) && minSize == 0 && maxSize >= Int32.MaxValue - 1)
                return true;

            if (maxSize == 0)
            {
                maxSize = Int32.MaxValue - 1;
            }

            bool notLogic = false;
            int foundStringIndex = 0;
            bool meetsMinSize = true;
            bool meetsMaxSize = true;
            bool metStringMatch = true;

            if (!string.IsNullOrEmpty(filter))
            {
                if (filter.StartsWith("!"))
                {
                    filter = filter.Substring(1);
                    notLogic = true;
                }

                foundStringIndex = FileName.IndexOf(filter, StringComparison.OrdinalIgnoreCase);
            }

            if (minSize > 0)
            {
                meetsMinSize = FileSize >= minSize;
            }
            if (maxSize < Int32.MaxValue - 1)
            {
                meetsMaxSize = FileSize <= maxSize;
            }

            if (foundStringIndex >= 0)
            {
                metStringMatch = notLogic ? false : true;
            }
            else
            {
                metStringMatch = false;
            }

            return (metStringMatch && meetsMinSize && meetsMaxSize) ? true : false;
        }

        public string FileName
        {
            get
            {
                if (_fileName == null && Item != null)
                {
                    _fileName = Item.FileName;
                }

                return _fileName;
            }
            set
            {
                if (Item.FileName != value)
                {
                    if (string.IsNullOrEmpty(_fileName))
                    {
                        _fileName = value;
                        Item.FileName = value;
                        OnPropertyChanged("FileNameOnly");
                    }
                    else
                    {
                        _fileName = value;
                        Item.FileName = value;
                        _fileNameOnly = null;
                        OnPropertyChanged("FileNameOnly");
                        OnPropertyChanged("FileSize");
                        OnPropertyChanged("FileSizeFormatted");
                        OnPropertyChanged("PixelFormatStringVisible");
                        OnPropertyChanged("WidthHeight");
                        OnPropertyChanged("WidthHeightVisible");
                        OnPropertyChanged("PixelFormatString");
                    }
                }
            }
        }

        public string FileSizeFormatted
        {
            get { return Item == null ? string.Empty : string.Format("{0:N0}K", (Item.FileSize / 1024)); }
        }

        public long FileSize
        {
            get { return Item == null ? 0 : Item.FileSize; }
            set
            {
                if (Item != null && Item.FileSize != value)
                {
                    Item.FileSize = value;
                }
            }
        }

        public void ClearBitmapCached()
        {
            Item.BitmapCached = null;
        }

        [RaisePropertyChangedForThisPropertyWhenOtherPropertyChanges("WidthHeight")]
        public Visibility WidthHeightVisible
        {
            get { return GetVisibility(LastOriginalBitmapHeight > 0D); }
        }

        public string PixelFormatString
        {
            get { return (Item == null || Item.BitmapCached == null) ? string.Empty : string.Format("{0}", Item.Bitmap.PixelFormat.ToString()); }
        }

        public Visibility PixelFormatStringVisible
        {
            get { return GetVisibility(Item == null ? false : (Item.BitmapCached != null)); }
        }

        public void ImageSourceLoaded(FileImage fi)
        {
            fi._lazyLoadingImageSource = false;
        }

        public void Rename(string newName)
        {
            if (FileName == newName)
                return;

            Item.Rename(newName);
            Item.MarkEverythingDirty(false);
            Thumbnail = null;
        }

        public UpdateAction UpdateIfDirty(bool vis, bool skip)
        {
            if (!skip && !vis)
            {
                FileImage fi = FileImage as FileImage;

                // outside visual viewing area
                if (Item.ThumbnailCached != null)
                {
                    if (ImageAppDataModels.FileImage.ThumbnailProvider.GetThumbsInMemory() > ImageAppDataModels.FileImage.ThumbnailProvider.TargetThumbs())
                    {
                        // was AllButFileBytes
                        Release(ReleaseOptions.All);
                        Dirty = true;
                        return UpdateAction.Unload;
                    }
                }

                if (BitmapLoaded || Item.ImageBytesCached != null)
                {
                    Release(ReleaseOptions.All);
                    Dirty = true;
                    return UpdateAction.Unload;
                }

                return UpdateAction.None;
            }
            else
            {
                if (Dirty && _thumbVisible)
                {
                    Dirty = false;
                    SetThumb(Item as FileImage);
                    return UpdateAction.Load;
                }
            }

            return UpdateAction.None;
        }

        public void SetThumb(FileImage fi)
        {
            if (SetThumbEvent != null)
            {
                SetThumbEvent(this, new ObjectEventArgs<FileImage>(fi));
            }
        }

        public static void DoSimilarityCheck(FileImageViewModel t, bool makeCopies)
        {
            double d = double.NaN;
            try
            {
                d = t.GetSimilarity(makeCopies);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
                Debug.Assert(false);
            }

            if (double.IsNaN(d))
                return;

            var _dispatcher = Application.Current.Dispatcher;
            if (_dispatcher != null)
            {
                _dispatcher.BeginInvoke(new TwoParamDelegate((thumb, val) =>
                {
                    var tn = thumb as FileImageViewModel;
                    var value = (double)val;
                    tn.SetSimilarity(tn.Other.FileImage as FileImage, value);
                }),
                DispatcherPriority.Normal, t, d);
            }
            else
            {
                t.SetSimilarity(t.Other.FileImage as FileImage, d);
            }
        }

        public static void DoBlurCheck(FileImageViewModel t)
        {
            if (t != null && t.ThumbVisible)
            {
                try
                {
                    double d = t.GetBlur(false);

                    if (double.IsNaN(d))
                    {
                        return;
                    }

                    var _dispatcher = Application.Current.Dispatcher;
                    if (_dispatcher != null)
                    {
                        _dispatcher.BeginInvoke(new TwoParamDelegate((tn, val) =>
                        {
                            FileImageViewModel vm = tn as FileImageViewModel;
                            double value = (double)val;

                            try
                            {
                                vm.SetBlur(value);
                                vm.UpdateBlur();
                            }
                            catch (Exception ex)
                            {
                                Console.WriteLine(ex.ToString());
                                Debug.WriteLine(ex.ToString());
                            }
                        }),
                        DispatcherPriority.ApplicationIdle, // verified okay
                        t, d);
                    }
                    else
                    {
                        t.SetBlur(d);
                        t.UpdateBlur();
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
        }

        private double GetSimilarity(bool makeCopies)
        {
            if (_other == null)
                return double.NaN;

            if (ThumbVisible)
            {
                return GetSimilarity(_other, makeCopies);
            }
            else
            {
                return double.NaN;
            }
        }

        private void SetThumbSimilarity(FileImage other, double d)
        {
            if (double.IsNaN(d))
            {
                SetSimilarity(other, double.NaN);
            }
            else
            {
                Similarity = d;
            }
        }

        private void RefreshSimilarity(FileImage other, bool makeCopies)
        {
            double d = GetSimilarity(makeCopies);
            SetSimilarity(other, d);
        }

        public double GetThumbBlur()
        {
            if (ThumbVisible)
            {
                return GetBlur();
            }
            else
            {
                return double.NaN;
            }
        }

        public void SetThumbBlur(double d)
        {
            if (double.IsNaN(d))
            {
                SetBlur(double.NaN);
            }
            else
            {
                Blur = d;
            }
        }

        private void RefreshBlur()
        {
            double d = GetBlur();
            SetBlur(d);
        }


        public void RaiseSimilarityGroupsChanged()
        {
            OnPropertyChanged("SimilarityGroups");
            OnPropertyChanged("SimilarityGroupStrings");
            OnPropertyChanged("SimilarityComparisonFiles");
        }


        public void RaiseHighlightBorderBrushChanged()
        {
        }

        public bool HasAny<T>(List<T> list1, List<T> list2)
        {
            for (int n = 0; n < list2.Count; n++)
            {
                for (int i = 0; i < list1.Count; i++)
                {
                    if (list1[i].Equals(list2[n]))
                        return true;
                }
            }

            return false;
        }

        public bool ContainsSubset<T>(List<T> list1, List<T> list2)
        {
            for (int n = 0; n < list2.Count; n++)
            {
                if (!list1.Contains(list2[n]))
                    return false;
            }

            return true;
        }

        public List<T> GetOverlap<T>(List<T> list1, List<T> list2)
        {
            List<T> results = new List<T>();

            for (int n = 0; n < list1.Count; n++)
            {
                for (int i = 0; i < list2.Count; i++)
                {
                    if (list1[n].Equals(list2[i]))
                        results.Add(list1[n]);
                }
            }

            return results;
        }

        public void SetSimilarityWith(FileImage fi, double d)
        {
            _currentOther = fi.FileName;
            var sim = _similarity.Get(fi.FileName, (str) => { return new SimilarityMeasure() { Similarity = double.NaN }; });
            sim.Similarity = d;
            OnPropertyChanged("Similarity");
            OnPropertyChanged("CurrentOther");
        }

        public double GetSimilarityWith(string s)
        {
            var sim = _similarity.Get(s, (str) => { return new SimilarityMeasure() { Similarity = double.NaN }; });
            return sim.Similarity;
        }

        public double GetSimilarity(FileImageViewModel other, bool makeCopies)
        {
            _currentOther = other.FileName;
            return Math.Floor(Item.Similarity(other.FileImage, makeCopies));
        }

        public double GetBlur()
        {
            return Item.GetBlur(false);
        }

        public double GetBlur(bool makeCopies)
        {
            return Item.GetBlur(makeCopies);
        }

        public double GetSimilarity(IFileImage other, bool makeCopies)
        {
            _currentOther = other.FileName;
            return Math.Floor(Item.Similarity(other, makeCopies));
        }

        public void NotifyImageChanged()
        {
            OnPropertyChanged("Bitmap", "TransformedBitmap", "ImageSource");
        }

        public void DetachEvents()
        {
            this.Item.PropertyChanged -= Item_PropertyChanged;
        }

        public void AttachEvents()
        {
            this.Item.PropertyChanged += new PropertyChangedEventHandler(Item_PropertyChanged);
        }

        public FileImageViewModel(FileImage fileImage, FilterCollection filterCollection, FileImageCollectionViewModel owningCollection)
        {
            this.Item = fileImage;
            this.Item.Filters = filterCollection;
            this.OwningCollection = owningCollection;

            AttachEvents();
        }

        public void MarkEverythingDirty(bool clearThumb)
        {
            Item.MarkEverythingDirty(clearThumb);

            if (clearThumb)
            {
                ThumbImageSourceCached = null;
            }
        }

        public Bitmap LoadBitmap()
        {
            return Item.EnsureBitmapLoaded();
        }

        public Bitmap PreloadThumbnail()
        {
            return Item.PreloadThumbnail();
        }

        public SingleThumbnail GetThumbnail(LoadOptions loadOptions, Action<FileImage, SingleThumbnail> callback, bool needThumbBytes = false)
        {
            return Item.GetThumbnail(loadOptions, callback, needThumbBytes);
        }

        void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName != null)
            {
                this.CheckAlsoRaise(e.PropertyName, _alsoRaisePropertiesLookup);

                switch (e.PropertyName)
                {
                    case "Thumbnail":
                        if ((this.Item as FileImage).ThumbnailCached != null && ThumbImageSourceCached != null)
                        {
                            ThumbImageSourceCached = null;
                        }
                        break;
                }
            }
            OnPropertyChanged(e.PropertyName);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public void ClearThumbnail()
        {
            Item.ClearThumbnailSilently();
            ThumbImageSourceCached = null;
        }

        [RaisePropertyChangedForThisPropertyWhenOtherPropertyChanges("OriginalBitmapWidth")]
        [RaisePropertyChangedForThisPropertyWhenOtherPropertyChanges("OriginalBitmapHeight")]
        public string WidthHeight
        {
            get { return string.Format("{0}x{1}", Item.OriginalBitmapWidth, Item.OriginalBitmapHeight); }
        }

        public bool Delete()
        {
            return Item.Delete();
        }

        public void SetCurrentWidthHeight(double width, double height)
        {
            if (!Util2.WithinWidthHeightTolerance(width, height, _currentWidth, _currentHeight))
            {
                if (_currentWidth == 0 && _currentHeight == 0)
                {
                    (Item as FileImage)._imageSourceWidth = width;
                    (Item as FileImage)._imageSourceHeight = height;

                    _currentWidth = width;
                    _currentHeight = height;

                    OnPropertyChanged("CurrentWidthHeight");
                }
                else
                {
                    Item.ImageSourceWidth = width;
                    Item.ImageSourceHeight = height;

                    _currentWidth = width;
                    _currentHeight = height;

                    OnPropertyChanged("CurrentWidthHeight");

                    Item.MarkImageSourceDirty();
                }
            }
        }

        public void Release(ReleaseOptions options)
        {
            Item.Release(options);

            if (options == ReleaseOptions.All)
            {
                (Item as FileImage).TransformedBitmap = null;
            }
        }

        public void Rotate(Rotation rotation, bool clockwise)
        {
            Item.PropertyChanged += new PropertyChangedEventHandler(Item_PropertyChangedForRotate);
            Item.BeginRotate(rotation, clockwise);
        }

        void Item_PropertyChangedForRotate(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "ThumbnailRotate")
            {
                Item.PropertyChanged -= Item_PropertyChangedForRotate;
                Item.MarkEverythingDirty(false);
                Thumbnail = null;
                ClearThumbnail();
                ClearFileBytes();
                Item.ThumbnailObject = null;
                Dirty = true;
                ThumbImageSourceCached = null;
                OnPropertyChanged("ThumbImageSourceCached");
                OnPropertyChanged("Thumbnail");

                if (RotateOrResizeDone != null)
                {
                    RotateOrResizeDone(this, new ObjectEventArgs<FileImage>(Item as FileImage));
                }
            }
        }

        public void ClearFileBytes()
        {
            Item.ImageBytesCached = null;
        }

        public void SetSimilarity(FileImage other, double p)
        {
            var sim = _similarity.Get(other.FileName, (s) => { return new SimilarityMeasure() { Similarity = p }; });
        }

        public void SetBlur(double p)
        {
            _blur = p;
        }

        public void UpdateBlur()
        {
            OnPropertyChanged("BlurIndicator");
            OnPropertyChanged("Blur");            
        }


        public Bitmap BeginResizeKeepAspectRatio(int width, int height)
        {
            ParameterizedThreadStart ts = new ParameterizedThreadStart(ResizeWorkerKeepAspectRatio);
            Thread t = new Thread(ts);
            t.Start(new ResizeInfo() { viewModel = this, f1 = this.Item, width = width, height = height, done = false });
            t.Name = "BeginResizeKeepAspectRatio";
            t.IsBackground = true;
            t.Priority = ThreadPriority.BelowNormal;
            return null;
        }

        private void ResizeWorkerKeepAspectRatio(object o)
        {
            ResizeInfo info = o as ResizeInfo;
            (info.viewModel as FileImageViewModel).ResizeKeepAspectRatio(ref info.width, ref info.height, info);
            while (!info.done)
            {
                Thread.Sleep(100);
            }
        }

        public void ResizeDelete(int width, int height, ResizeInfo resizeInfo)
        {
            if (Item.Deleted)
                return;

            FileImage fi = Item as FileImage;
            fi.RotateOrResizeDone += new EventHandler<ObjectEventArgs<ImageAppDataModels.FileImage, ResizeInfo>>(ResizeKeepAspectRatioDone);
            BeginResizeKeepAspectRatio(width, height);

            //ResizeKeepAspectRatio(ref width, ref height, resizeInfo);

            //if (Item._oldName != Item.FileName)
            //{
            //    DispatcherTimer dt = new DispatcherTimer()
            //    {
            //        Interval = new TimeSpan(0, 0, 1),
            //        IsEnabled = true,
            //        Tag = new ViewModelWidthHeight() { width = width, height = height, vm = this },
            //    };
            //    dt.Tick += new EventHandler(dt_Tick_Delete);
            //    dt.Start();
            //}
            //else
            //{
            //    if (resizeInfo.bytes != null)
            //    {
            //        File.WriteAllBytes(Item.FileName, resizeInfo.bytes);
            //        Thumbnail = null;
            //        ClearThumbnail();
            //        ClearFileBytes();
            //        Item.ThumbnailObject = null;
            //        Dirty = true;
            //        ThumbImageSourceCached = null;
            //        OnPropertyChanged("ThumbImageSourceCached");
            //        OnPropertyChanged("Thumbnail");
            //        RotateOrResizeDone.RaiseEvent(this, Item);
            //    }
            //}
        }

        public bool AttemptDeleteFile(ViewModelWidthHeight info)
        {
            try
            {
                if (this.FileImage.Deleted)
                {
                    return true;
                }

                if (!info.vm.Delete())
                {
                    info.tries++;
                }
                else
                {
                    if (info.vm.FileImage.Deleted)
                    {
                        if (OwningCollection != null)
                        {
                            OwningCollection.RemoveByFileName(info.vm.FileName);
                        }
                    }

                    return true;
                }

                return false;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
                return false;
            }
        }

        void dt_Tick_Delete(object sender, EventArgs e)
        {
            DispatcherTimer dt = sender as DispatcherTimer;
            ViewModelWidthHeight info = dt.Tag as ViewModelWidthHeight;

            if (AttemptDeleteFile(info))
            {
                dt.Tick -= dt_Tick_Delete;
                dt.Stop();
                return;
            }
        }

        public bool ResizeKeepAspectRatio(ref int width, ref int height, ResizeInfo resizeInfo)
        {
            if (width <= 0)
                width = 1024;

            if (height <= 0)
                height = 768;

            Bitmap bm = this.FileImage.EnsureBitmapLoaded();

            if ((width < height && bm.Width > bm.Height) ||
                (width > height && bm.Width < bm.Height))
            {
                int tempHeight = height;
                height = width;
                width = tempHeight;
            }

            double scaleFactor = Scaling.GetScaledImageDimensions((double)bm.Width, (double)bm.Height, (double)width, (double)height);
            width = (int)((double)bm.Width * scaleFactor);
            height = (int)((double)bm.Height * scaleFactor);

            this.FileImage.Resize(width, height, resizeInfo);
            return true;
        }

        public void Resize(int width, int height)
        {
            FileImage fi = Item as FileImage;
            fi.RotateOrResizeDone += new EventHandler<ObjectEventArgs<FileImage, ResizeInfo>>(fi_RotateOrResizeDone);
            Item.BeginResize(width, height);
        }

        void fi_RotateOrResizeDone(object sender, ObjectEventArgs<FileImage, ResizeInfo> e)
        {
            (Item as FileImage).RotateOrResizeDone -= fi_RotateOrResizeDone;

            e.obj2.tmr = new Timer(
                TimerCallback,
                new FileNameAndBytes() { bytes = e.obj2.bytes, fileName = e.obj2.newFileName, fileImage = sender as FileImage, info = e.obj2 },
                2000, 2050);
        }

        void TimerCallback(object state)
        {
            try
            {
                FileNameAndBytes info = state as FileNameAndBytes;

                if (info.entered)
                {
                    return;
                }

                info.entered = true;
                Timer tmr = info.info.tmr;

                try
                {
                    FileImage fi = info.fileImage as FileImage;

                    File.WriteAllBytes(info.fileName, info.bytes);

                    if (fi != null)
                    {
                        fi.MarkEverythingDirty(false);
                        fi.Thumbnail = null;
                        this.ClearThumbnail();
                        this.ClearFileBytes();
                        Dirty = true;
                        fi.ImageBytes = null;
                        fi.FileSize = 0;
                        fi.RaisePropertyChangedUIThread("FileSize");
                        fi.RaisePropertyChangedUIThread("FileSizeFormatted");
                        fi.RaisePropertyChangedUIThread("Bitmap");
                        fi.RaisePropertyChangedUIThread("ImageSource");
                        fi.RaisePropertyChangedUIThread("Thumbnail");
                        UpdateIfDirty(ThumbVisible, false);
                    }
                    tmr.Dispose();
                    info.info.tmr = null;
                    info.info.done = true;
                }
                catch (Exception e2)
                {
                    Console.WriteLine(e2.ToString());
                    info.tries++;
                }

                info.entered = false;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        public void Preload(ICanBePreloaded<Bitmap> preloadSource)
        {
            // For Thumbnails
            if (ThumbVisible)
            {
                if (Dirty && ThumbImageSourceCached == null && Item.ThumbnailCached == null)
                {
                    var hbi = SetSuspendPropertyChanged(true);
                    Bitmap bm = PreloadThumbnail();
                    // you're right - better run if we're here... (unless we need to fix values)
                    SetSuspendPropertyChanged(false, false);
                    OnPropertyChangedUIThread("Bitmap");
                    SetSuspendPropertyChanged(false, true);
                }
            }

            UpdateIfDirty(ThumbVisible, false);
        }

        public bool IsAlreadyLoaded(ICanBePreloaded<Bitmap> preloadSource)
        {
            return (!ThumbVisible);
        }

        public override string ToString()
        {
            return "FileImageViewModel: " + Item.ToString();
        }

        public void ResizeKeepAspectRatioDone(object sender, ObjectEventArgs<ImageAppDataModels.FileImage, ResizeInfo> e)
        {
            (sender as FileImage).RotateOrResizeDone -= ResizeKeepAspectRatioDone;
            Application.Current.Dispatcher.BeginInvoke(new VoidDelegate(() =>
            {
                ResizeInfo resizeInfo = e.obj2;

                if (Item._oldName != Item.FileName)
                {
                    DispatcherTimer dt = new DispatcherTimer()
                    {
                        Interval = new TimeSpan(0, 0, 1),
                        IsEnabled = true,
                        Tag = new ViewModelWidthHeight() { width = resizeInfo.width, height = resizeInfo.height, vm = this },
                    };
                    dt.Tick += new EventHandler(dt_Tick_Delete);
                    dt.Start();
                }
                else
                {
                    if (resizeInfo.bytes != null)
                    {
                        File.WriteAllBytes(Item.FileName, resizeInfo.bytes);
                        Thumbnail = null;
                        ClearThumbnail();
                        ClearFileBytes();
                        Item.ThumbnailObject = null;
                        Dirty = true;
                        ThumbImageSourceCached = null;
                        OnPropertyChanged("ThumbImageSourceCached");
                        OnPropertyChanged("Thumbnail");
                        RotateOrResizeDone.RaiseEvent(this, Item);
                    }
                }
            }));
        }        
    }
}
